
import "DPI-C" function read_elf(input string filename);
import "DPI-C" function byte get_section(output longint address, output longint len);
import "DPI-C" context function void read_section(input longint address, inout byte buffer[]);

// Top Module for VCS and Verdi
module soc_testharness #(
    parameter int unsigned AXI_ID_WIDTH      = 9,
    parameter int unsigned AXI_ADDR_WIDTH    = 48,
    parameter int unsigned AXI_DATA_WIDTH    = 128,
    parameter int unsigned AXI_USER_WIDTH    = 2
)();

wire   [1:0]   core_in_core_id;
wire   [7:0]   core_in_interrupt;
wire           core_in_ipi;


bit            axi_aw_valid;
bit            axi_aw_ready;
bit   [47:0]   axi_aw_addr;
bit   [AXI_ID_WIDTH-1:0]    axi_aw_id;
bit   [7:0]    axi_aw_len;
bit   [2:0]    axi_aw_size;
bit   [1:0]    axi_aw_burst;
bit   [3:0]    axi_aw_cache;
bit   [2:0]    axi_aw_prot;
bit            axi_w_valid;
bit            axi_w_ready;
bit   [127:0]  axi_w_data;
bit   [15:0]   axi_w_strb;
bit            axi_w_last;
bit            axi_b_valid;
bit            axi_b_ready;
bit   [AXI_ID_WIDTH-1:0]    axi_b_id;
bit   [1:0]    axi_b_resp;
bit            axi_ar_valid;
bit            axi_ar_ready;
bit   [47:0]   axi_ar_addr;
bit   [AXI_ID_WIDTH-1:0]    axi_ar_id;
bit   [7:0]    axi_ar_len;
bit   [2:0]    axi_ar_size;
bit   [1:0]    axi_ar_burst;
bit   [3:0]    axi_ar_cache;
bit   [2:0]    axi_ar_prot;
bit            axi_r_valid;
bit            axi_r_ready;
bit   [127:0]  axi_r_data;
bit   [AXI_ID_WIDTH-1:0]    axi_r_id;
bit   [1:0]    axi_r_resp;
bit            axi_r_last;



reg clk;
initial begin 
  clk = 0;
  forever begin
    #(2) clk = ~clk;
  end
end

reg reset;
initial begin
  reset = 1;
  #10;
  reset = 1;
  #80;
  reset = 0;

  // #2000000
  // $finish;  

end

`define  DUMMY_LOG
// `define BIOS_TEST    1

`ifndef BIOS_TEST
`define  CPU_TEST    1
`endif


reg [47:0]  cpu_awaddr;
reg [3:0]   cpu_awlen;
reg [15:0]  cpu_wstrb;
reg         cpu_wvalid;
reg         cpu_wready;
reg [127:0] cpu_w_data;

`ifdef DUMMY_LOG

`ifdef CPU_TEST

always @(posedge clk)
begin
  cpu_awaddr[47:0] <= soc_wrap.io_axi_aw_payload_addr[47:0];
  cpu_awlen[3:0]   <= soc_wrap.io_axi_aw_payload_len[3:0];
  cpu_wvalid       <= soc_wrap.io_axi_w_valid;
  cpu_wready       <= soc_wrap.io_axi_w_ready;
  cpu_wstrb        <= soc_wrap.io_axi_w_payload_strb;
  cpu_w_data       <= soc_wrap.io_axi_w_payload_data[127:0];
end

`else 

always @(posedge clk)
begin
  cpu_awaddr[47:0] <= soc_wrap.core.io_axi_aw_payload_addr[47:0];
  cpu_awlen[3:0]   <= soc_wrap.core.io_axi_aw_payload_len[3:0];
  cpu_wvalid       <= soc_wrap.core.io_axi_w_valid;
  cpu_wready       <= soc_wrap.core.io_axi_w_ready;
  cpu_wstrb        <= soc_wrap.core.io_axi_w_payload_strb;
  cpu_w_data       <= soc_wrap.core.io_axi_w_payload_data[127:0];
end


`endif


always @(posedge clk) begin
     if ((cpu_awlen[3:0] == 4'b0) &&
         (cpu_awaddr[31:0] == 32'h1FF01_0000) &&
          cpu_wvalid &&
          cpu_wready) 
     begin
        if(cpu_wstrb[15:0] == 16'hf) 
           begin
              $write("%c", cpu_w_data[7:0]);
           end
        else if(cpu_wstrb[15:0] == 16'hf0)
           begin
              $write("%c", cpu_w_data[39:32]);
           end
        else if(cpu_wstrb[15:0] == 16'hf00)
           begin
              $write("%c", cpu_w_data[71:64]);
           end
        else if(cpu_wstrb[15:0] == 16'hf000)
           begin
              $write("%c", cpu_w_data[103:96]);
           end
    end
end


`endif


assign core_in_core_id = {1'b0, 1'b0};
assign core_in_interrupt = {1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0};
assign core_in_ipi = 1'b0;


reg sys_exit;

`ifdef DUMMY_LOG
always @(posedge clk) begin
     if ((cpu_awaddr[31:0] == 32'h1FF8_0000) &&
          cpu_wvalid &&
          cpu_wready
     ) begin
        if(cpu_wstrb[15:0] == 16'hff &&
           cpu_w_data[7:0] == 8'b00001111) 
           begin
              sys_exit <= 1;
              // $finish;
              $stop;
           end
    end
end
`endif


`ifdef VERDI_DUMP
initial begin
      /// method 1: this will dump only ct_top module
      $fsdbDumpon;
      $fsdbDumpfile("soc_testharness.fsdb");
      $fsdbDumpvars(0, soc_testharness);

      /// method 2: this will dump all module
      // $fsdbDumpvars();
end
`endif


`ifdef CPU_TEST

Top soc_wrap (
   .io_axi_aw_valid         (axi_aw_valid             ), //o
   .io_axi_aw_ready         (axi_aw_ready             ), //i
   .io_axi_aw_payload_addr  (axi_aw_addr[47:0]        ), //o
   .io_axi_aw_payload_id    (axi_aw_id[AXI_ID_WIDTH-1:0]           ), //o
   .io_axi_aw_payload_len   (axi_aw_len[7:0]          ), //o
   .io_axi_aw_payload_size  (axi_aw_size[2:0]         ), //o
   .io_axi_aw_payload_burst (axi_aw_burst[1:0]        ), //o
   .io_axi_aw_payload_cache (axi_aw_cache[3:0]        ), //o
   .io_axi_aw_payload_prot  (axi_aw_prot[2:0]         ), //o
   
   .io_axi_w_valid          (axi_w_valid              ), //o
   .io_axi_w_ready          (axi_w_ready              ), //i
   .io_axi_w_payload_data   (axi_w_data[127:0]        ), //o
   .io_axi_w_payload_strb   (axi_w_strb[15:0]         ), //o
   .io_axi_w_payload_last   (axi_w_last               ), //o
   
   .io_axi_b_valid          (axi_b_valid              ), //i
   .io_axi_b_ready          (axi_b_ready              ), //o
   .io_axi_b_payload_id     (axi_b_id[AXI_ID_WIDTH-1:0]            ), //i
   .io_axi_b_payload_resp   (axi_b_resp               ), //i
   
   .io_axi_ar_valid         (axi_ar_valid             ), //o
   .io_axi_ar_ready         (axi_ar_ready             ), //i
   .io_axi_ar_payload_addr  (axi_ar_addr[47:0]        ), //o
   .io_axi_ar_payload_id    (axi_ar_id[AXI_ID_WIDTH-1:0]           ), //o
   .io_axi_ar_payload_len   (axi_ar_len[7:0]          ), //o
   .io_axi_ar_payload_size  (axi_ar_size[2:0]         ), //o
   .io_axi_ar_payload_burst (axi_ar_burst[1:0]        ), //o
   .io_axi_ar_payload_cache (axi_ar_cache[3:0]        ), //o
   .io_axi_ar_payload_prot  (axi_ar_prot[2:0]         ), //o    
   
   .io_axi_r_valid          (axi_r_valid              ), //i
   .io_axi_r_ready          (axi_r_ready              ), //o
   .io_axi_r_payload_data   (axi_r_data[127:0]        ), //i
   .io_axi_r_payload_id     (axi_r_id[AXI_ID_WIDTH-1:0]            ), //i
   .io_axi_r_payload_last   (axi_r_last               ), //i    
   .io_axi_r_payload_resp   (axi_r_resp               ), //i 
   
   // .io_core_in_core_id      (core_in_core_id[1:0]      ), //i
   .io_core_in_interrupt    (core_in_interrupt[7:0]    ), //i
   // .io_core_in_ipi          (core_in_ipi               ), //i    
   
   .io_soft_fpu_0_value     (128'h0                    ), //i
   .io_soft_fpu_0_vzoui     (5'h0                      ), //i
   .io_soft_fpu_1_value     (128'h0                    ), //i
   .io_soft_fpu_1_vzoui     (5'h0                      ), //i
   .io_soft_fpu_2_value     (128'h0                    ), //i
   .io_soft_fpu_2_vzoui     (5'h0                      ), //i
   .io_soft_fpu_3_value     (128'h0                    ), //i
   .io_soft_fpu_3_vzoui     (5'h0                      ), //i
   .reset                   (reset                     ), //i
   .clk                     (clk                       ), //i

   // unused output port
   .io_soft_fpu_0_info_a            (),
   .io_soft_fpu_0_info_b            (),
   .io_soft_fpu_0_info_c            (),
   .io_soft_fpu_0_info_cat          (),
   .io_soft_fpu_0_info_op           (),
   .io_soft_fpu_0_info_size         (),
   .io_soft_fpu_0_info_prf          (),
   .io_soft_fpu_0_info_rob          (),
   .io_soft_fpu_0_info_brq          (),
   .io_soft_fpu_0_info_fcc          (),
   .io_soft_fpu_0_info_rm           (),
   .io_soft_fpu_0_info_latency      (),
   .io_soft_fpu_0_info_write_type   (),
   .io_soft_fpu_0_valid             (),
   .io_soft_fpu_1_info_a            (),
   .io_soft_fpu_1_info_b            (),
   .io_soft_fpu_1_info_c            (),
   .io_soft_fpu_1_info_cat          (),
   .io_soft_fpu_1_info_op           (),
   .io_soft_fpu_1_info_size         (),
   .io_soft_fpu_1_info_prf          (),
   .io_soft_fpu_1_info_rob          (),
   .io_soft_fpu_1_info_brq          (),
   .io_soft_fpu_1_info_fcc          (),
   .io_soft_fpu_1_info_rm           (),
   .io_soft_fpu_1_info_latency      (),
   .io_soft_fpu_1_info_write_type   (),
   .io_soft_fpu_1_valid             (),
   .io_soft_fpu_2_info_a            (),
   .io_soft_fpu_2_info_b            (),
   .io_soft_fpu_2_info_c            (),
   .io_soft_fpu_2_info_cat          (),
   .io_soft_fpu_2_info_op           (),
   .io_soft_fpu_2_info_size         (),
   .io_soft_fpu_2_info_prf          (),
   .io_soft_fpu_2_info_rob          (),
   .io_soft_fpu_2_info_brq          (),
   .io_soft_fpu_2_info_fcc          (),
   .io_soft_fpu_2_info_rm           (),
   .io_soft_fpu_2_info_latency      (),
   .io_soft_fpu_2_info_write_type   (),
   .io_soft_fpu_2_valid             (),
   .io_soft_fpu_3_info_a            (),
   .io_soft_fpu_3_info_b            (),
   .io_soft_fpu_3_info_c            (),
   .io_soft_fpu_3_info_cat          (),
   .io_soft_fpu_3_info_op           (),
   .io_soft_fpu_3_info_size         (),
   .io_soft_fpu_3_info_prf          (),
   .io_soft_fpu_3_info_rob          (),
   .io_soft_fpu_3_info_brq          (),
   .io_soft_fpu_3_info_fcc          (),
   .io_soft_fpu_3_info_rm           (),
   .io_soft_fpu_3_info_latency      (),
   .io_soft_fpu_3_info_write_type   (),
   .io_soft_fpu_3_valid             ()
);

`else 

Soc soc_wrap(
   .clk                     (clk                      ),
   .reset                   (reset                    ),
   .io_axi_aw_valid         (axi_aw_valid             ),
   .io_axi_aw_ready         (axi_aw_ready),
   .io_axi_aw_payload_addr  (axi_aw_addr),
   .io_axi_aw_payload_id    (axi_aw_id),
   .io_axi_aw_payload_len   (axi_aw_len),
   .io_axi_aw_payload_size  (axi_aw_size),
   .io_axi_aw_payload_burst (axi_aw_burst),
   .io_axi_aw_payload_cache (axi_aw_cache),
   .io_axi_aw_payload_prot  (axi_aw_prot),
   .io_axi_w_valid          (axi_w_valid),
   .io_axi_w_ready          (axi_w_ready),
   .io_axi_w_payload_data   (axi_w_data),
   .io_axi_w_payload_strb   (axi_w_strb),
   .io_axi_w_payload_last   (axi_w_last),
   .io_axi_b_valid          (axi_b_valid),
   .io_axi_b_ready          (axi_b_ready),
   .io_axi_b_payload_id     (axi_b_id),
   .io_axi_b_payload_resp   (axi_b_resp),
   .io_axi_ar_valid         (axi_ar_valid),
   .io_axi_ar_ready         (axi_ar_ready),
   .io_axi_ar_payload_addr  (axi_ar_addr),
   .io_axi_ar_payload_id    (axi_ar_id),
   .io_axi_ar_payload_len   (axi_ar_len),
   .io_axi_ar_payload_size  (axi_ar_size),
   .io_axi_ar_payload_burst (axi_ar_burst),
   .io_axi_ar_payload_cache (axi_ar_cache),
   .io_axi_ar_payload_prot  (axi_ar_prot),
   .io_axi_r_valid          (axi_r_valid),
   .io_axi_r_ready          (axi_r_ready),
   .io_axi_r_payload_data   (axi_r_data),
   .io_axi_r_payload_id     (axi_r_id),
   .io_axi_r_payload_resp   (axi_r_resp),
   .io_axi_r_payload_last   (axi_r_last),
   .io_apb_PADDR            (),
   .io_apb_PSEL             (),
   .io_apb_PENABLE          (),
   .io_apb_PREADY           (1'b1),
   .io_apb_PWRITE           (),
   .io_apb_PWDATA           (),
   .io_apb_PRDATA           ('0),
   .io_core_in_core_id      (core_in_core_id),
   .io_core_in_interrupt    (core_in_interrupt),
   .io_core_in_ipi          (core_in_ipi)
   );

`endif


// if we define BIOS_TEST, cpu reset to 0x1c000000, in internal rom.

`ifdef BIOS_TEST

assign  axi_aw_ready = 0;
assign  axi_w_ready = 0;
assign  axi_b_valid = 0;
assign  axi_b_id = 0;
assign  axi_b_resp = 0;
assign  axi_ar_ready = 0;
assign  axi_r_valid = 0;
assign  axi_r_data = 0;
assign  axi_r_id = 0;
assign  axi_r_resp = 0;
assign  axi_r_last = 0;

`else 


reg [63:0] sim_cycles;

always @(posedge clk) begin : proc_sim_cycles
    if(reset) begin
        sim_cycles <= 0;
    end else begin
        sim_cycles <= sim_cycles + 1'b1;
    end
end

logic                        req;
logic                        we;
logic [AXI_ADDR_WIDTH-1:0]   addr;
logic [AXI_DATA_WIDTH/8-1:0] be;
logic [AXI_DATA_WIDTH-1:0]   rdata;
logic [AXI_DATA_WIDTH-1:0]   wdata;

axi_mem_if #(
    .AXI_ID_WIDTH    (AXI_ID_WIDTH),
    .AXI_ADDR_WIDTH  (AXI_ADDR_WIDTH),
    .AXI_DATA_WIDTH  (AXI_DATA_WIDTH),
    .AXI_USER_WIDTH  (AXI_USER_WIDTH)
) mem_if (
    .clk_i            (clk),
    .rst_ni           (~reset),
    .aw_valid         (axi_aw_valid), 
    .aw_ready         (axi_aw_ready), 
    .aw_addr          (axi_aw_addr), 
    .aw_id            (axi_aw_id), 
    .aw_len           (axi_aw_len), 
    .aw_size          (axi_aw_size), 
    .aw_burst         (axi_aw_burst), 
    .aw_cache         (axi_aw_cache), 
    .aw_prot          (axi_aw_prot),
    .w_valid          (axi_w_valid), 
    .w_ready          (axi_w_ready), 
    .w_data           (axi_w_data), 
    .w_strb           (axi_w_strb), 
    .w_last           (axi_w_last),
    .b_valid          (axi_b_valid), 
    .b_ready          (axi_b_ready),
    .b_id             (axi_b_id),
    .b_resp           (axi_b_resp),
    .ar_valid         (axi_ar_valid), 
    .ar_ready         (axi_ar_ready), 
    .ar_addr          (axi_ar_addr), 
    .ar_id            (axi_ar_id), 
    .ar_len           (axi_ar_len), 
    .ar_size          (axi_ar_size), 
    .ar_burst         (axi_ar_burst), 
    .ar_cache         (axi_ar_cache), 
    .ar_prot          (axi_ar_prot),
    .r_valid          (axi_r_valid), 
    .r_ready          (axi_r_ready), 
    .r_data           (axi_r_data), 
    .r_id             (axi_r_id), 
    .r_resp           (axi_r_resp), 
    .r_last           (axi_r_last),

    .req_o            (req),
    .we_o             (we),
    .addr_o           (addr),
    .be_o             (be),
    .data_o           (wdata),
    .data_i           (rdata)
);


slave_ram sys_ram(
    .clk              (clk),
    .reset            (reset),
    .req_i            (req),
    .we_i             (we),
    .addr_i           (addr),
    .be_i             (be),
    .data_i           (wdata),
    .data_o           (rdata)
);


//

reg start_dump;
always @(posedge clk) begin
    if (reset) begin
        start_dump <= 0;
    end
    else if (sim_cycles >= 64'hd345 & ~start_dump) begin
        start_dump <= 1;
        $fsdbDumpon;
        $fsdbDumpfile("soc_testharness.fsdb");
        $fsdbDumpvars(0, soc_testharness);
    end
end


///NOTE: The following way is too too slow. Deprecated!
/// --------------------- Initial --------------------------- ///
// string binary = "vmlinux";

// for faster simulation we can directly preload the ELF
// Note that we are loosing the capabilities to use risc-fesvr though
// initial begin
//     automatic logic [15:0][7:0] mem_row;
//     longint address, len;
//     byte buffer[];
//     int percent = 0;
//     if (binary != "") begin
//         $display("Preloading ELF: %s", binary);
//         void'(read_elf(binary));
//         // wait with preloading, otherwise randomization will overwrite the existing value
//         wait(clk);
//         // while there are more sections to process
//         while (get_section(address, len)) begin
//             automatic int num_words = (len+15)/16;
//             automatic int per_tmp = num_words / 100 ;
//             automatic int ii = 0;
//             automatic int iii = 0;
//             $display("Loading Address: %x, Length: %x", address, len);
//             $display("Length num_words: %x", num_words);
//             buffer = new [num_words*16];
//             void'(read_section(address, buffer));
//             // preload memories
//             // 128-bit
//             for ( int i = 0; i < num_words; i++) begin
//                 mem_row = '0;
//                 iii++;
//                 for (int j = 0; j < 16; j++) begin
//                     mem_row[j] = buffer[i*16 + j];
//                 end
//                 if (per_tmp == iii) begin
//                     iii = 0;
//                     ii ++;
//                     $display("Length num_words percent: %d", ii);
//                 end
//                 sys_ram.ram_mem[address[47:0]>>4 + i][127:0] = mem_row;

//             end
//         end
//         $display("Loading ELF Successfully!");
//     end
// end


`endif // BIOS_TEST

endmodule